1   /*
2    * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.
8    *
9    * This code is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12   * version 2 for more details (a copy is included in the LICENSE file that
13   * accompanied this code).
14   *
15   * You should have received a copy of the GNU General Public License version
16   * 2 along with this work; if not, write to the Free Software Foundation,
17   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18   *
19   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20   * or visit www.oracle.com if you need additional information or have any
21   * questions.
22   */
23  
24  /**
25   * @test
26   * @bug 6480504
27   * @summary Test that client-provided data in the extra field is written and
28   * read correctly, taking into account the JAR_MAGIC written into the extra
29   * field of the first entry of JAR files.
30   * @author Dave Bristor
31   */
32  
33  import java.io.*;
34  import java.nio.charset.Charset;
35  import java.util.Arrays;
36  import java.util.jar.*;
37  import java.util.zip.*;
38  
39  /**
40   * Tests that the get/set operations on extra data in zip and jar files work
41   * as advertised.  The base class tests ZIP files, the member class
42   * TestJarExtra checks JAR files.
43   */
44  public class TestExtra {
45      final static int JAR_MAGIC = 0xcafe; // private IN JarOutputStream.java
46      final static int TEST_HEADER = 0xbabe;
47  
48      final static Charset ascii = Charset.forName("ASCII");
49  
50      // ZipEntry extra data
51      final static byte[][] extra = new byte[][] {
52          ascii.encode("hello, world").array(),
53          ascii.encode("foo bar").array()
54      };
55  
56      // For naming entries in JAR/ZIP streams
57      int count = 1;
58  
59      // Use byte arrays instead of files
60      ByteArrayOutputStream baos;
61  
62      // JAR/ZIP content written here.
63      ZipOutputStream zos;
64  
65      public static void realMain(String[] args) throws Throwable{
66          new TestExtra().testHeaderPlusData();
67  
68          new TestJarExtra().testHeaderPlusData();
69          new TestJarExtra().testHeaderOnly();
70          new TestJarExtra().testClientJarMagic();
71      }
72  
73      TestExtra() {
74          try {
75              baos = new ByteArrayOutputStream();
76              zos = getOutputStream(baos);
77          } catch (Throwable t) {
78              unexpected(t);
79          }
80      }
81  
82      /** Test that a header + data set by client works. */
83      void testHeaderPlusData() throws IOException {
84          for (byte[] b : extra) {
85              ZipEntry ze = getEntry();
86              byte[] data = new byte[b.length + 4];
87              set16(data, 0, TEST_HEADER);
88              set16(data, 2, b.length);
89              for (int i = 0; i < b.length; i++) {
90                  data[i + 4] = b[i];
91              }
92              ze.setExtra(data);
93              zos.putNextEntry(ze);
94          }
95          zos.close();
96  
97          ZipInputStream zis = getInputStream();
98  
99          ZipEntry ze = zis.getNextEntry();
100         checkEntry(ze, 0, extra[0].length);
101 
102         ze = zis.getNextEntry();
103         checkEntry(ze, 1, extra[1].length);
104     }
105 
106     /** Test that a header only (i.e., no extra "data") set by client works. */
107     void testHeaderOnly() throws IOException {
108         ZipEntry ze = getEntry();
109         byte[] data = new byte[4];
110         set16(data, 0, TEST_HEADER);
111         set16(data, 2, 0); // Length of data is 0.
112         ze.setExtra(data);
113         zos.putNextEntry(ze);
114 
115         zos.close();
116 
117         ZipInputStream zis = getInputStream();
118 
119         ze = zis.getNextEntry();
120         byte[] e = ze.getExtra();
121         check(e.length == 8, "expected extra length is 8, got " + e.length);
122         checkEntry(ze, 0, 0);
123     }
124 
125     /** Tests the client providing extra data which uses JAR_MAGIC header. */
126     void testClientJarMagic() throws IOException {
127         ZipEntry ze = getEntry();
128         byte[] data = new byte[8];
129 
130         set16(data, 0, TEST_HEADER);
131         set16(data, 2, 0); // Length of data is 0.
132         set16(data, 4, JAR_MAGIC);
133         set16(data, 6, 0); // Length of data is 0.
134 
135         ze.setExtra(data);
136         zos.putNextEntry(ze);
137 
138         zos.close();
139 
140         ZipInputStream zis = getInputStream();
141         ze = zis.getNextEntry();
142         byte[] e = ze.getExtra();
143         check(e.length == 8, "expected extra length is 8, got " + e.length);
144         checkEntry(ze, 0, 0);
145     }
146 
147 
148     /** Check that the entry's extra data is correct. */
149     void checkEntry(ZipEntry ze, int count, int dataLength) {
150         byte[] extraData = ze.getExtra();
151         byte[] data = getField(TEST_HEADER, extraData);
152         if (!check(data != null, "unexpected null data for TEST_HEADER")) {
153             return;
154         }
155 
156         if (dataLength == 0) {
157             check(data.length == 0, "unexpected non-zero data length for TEST_HEADER");
158         } else {
159             check(Arrays.equals(extra[count], data),
160                   "failed to get entry " + ze.getName()
161                   + ", expected " + new String(extra[count]) + ", got '" + new String(data) + "'");
162         }
163     }
164 
165     /** Look up descriptor in data, returning corresponding byte[]. */
166     static byte[] getField(int descriptor, byte[] data) {
167         byte[] rc = null;
168         try {
169             int i = 0;
170             while (i < data.length) {
171                 if (get16(data, i) == descriptor) {
172                     int length = get16(data, i + 2);
173                     rc = new byte[length];
174                     for (int j = 0; j < length; j++) {
175                         rc[j] = data[i + 4 + j];
176                     }
177                     return rc;
178                 }
179                 i += get16(data, i + 2) + 4;
180             }
181         } catch (ArrayIndexOutOfBoundsException e) {
182             // descriptor not found
183         }
184         return rc;
185     }
186 
187     ZipInputStream getInputStream() {
188         return new ZipInputStream(
189             new ByteArrayInputStream(baos.toByteArray()));
190     }
191 
192     ZipOutputStream getOutputStream(ByteArrayOutputStream baos) throws IOException {
193         return new ZipOutputStream(baos);
194     }
195 
196     ZipInputStream getInputStream(ByteArrayInputStream bais) throws IOException {
197         return new ZipInputStream(bais);
198     }
199 
200     ZipEntry getEntry() { return new ZipEntry("zip" + count++ + ".txt"); }
201 
202 
203     private static int get16(byte[] b, int off) {
204         return (b[off] & 0xff) | ((b[off+1] & 0xff) << 8);
205     }
206 
207     private static void set16(byte[] b, int off, int value) {
208         b[off+0] = (byte)value;
209         b[off+1] = (byte)(value >> 8);
210     }
211 
212     /** Test extra field of a JAR file. */
213     static class TestJarExtra extends TestExtra {
214         ZipOutputStream getOutputStream(ByteArrayOutputStream baos) throws IOException {
215             return new JarOutputStream(baos);
216         }
217 
218         ZipInputStream getInputStream(ByteArrayInputStream bais) throws IOException {
219             return new JarInputStream(bais);
220         }
221 
222         ZipEntry getEntry() { return new ZipEntry("jar" + count++ + ".txt"); }
223 
224         void checkEntry(ZipEntry ze, int count, int dataLength) {
225             // zeroth entry should have JAR_MAGIC
226             if (count == 0) {
227                 byte[] extraData = ze.getExtra();
228                 byte[] data = getField(JAR_MAGIC, extraData);
229                 if (!check(data != null, "unexpected null data for JAR_MAGIC")) {
230                     check(data.length != 0, "unexpected non-zero data length for JAR_MAGIC");
231                 }
232             }
233             // In a jar file, the first ZipEntry should have both JAR_MAGIC
234             // and the TEST_HEADER, so check that also.
235             super.checkEntry(ze, count, dataLength);
236         }
237     }
238 
239     //--------------------- Infrastructure ---------------------------
240     static volatile int passed = 0, failed = 0;
241     static void pass() {passed++;}
242     static void fail() {failed++; Thread.dumpStack();}
243     static void fail(String msg) {System.out.println(msg); fail();}
244     static void unexpected(Throwable t) {failed++; t.printStackTrace();}
245     static void check(boolean cond) {if (cond) pass(); else fail();}
246     static boolean check(boolean cond, String msg) {if (cond) pass(); else fail(msg); return cond; }
247     static void equal(Object x, Object y) {
248         if (x == null ? y == null : x.equals(y)) pass();
249         else fail(x + " not equal to " + y);}
250     public static void main(String[] args) throws Throwable {
251         try {realMain(args);} catch (Throwable t) {unexpected(t);}
252         System.out.println("\nPassed = " + passed + " failed = " + failed);
253         if (failed > 0) throw new Error("Some tests failed");}
254 }